import { EUVDirective, EUVDirectiveInformation } from "../interface/euv-directive";
import { EUVNode } from "../interface/euv-node";


function escapeRegExp(str: string) { // 来自 MDN 文档
    //$&表示整个被匹配的字符串
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function 编译表达式(
    str: string
): {f: Function, needArgs: string[]} | null {
    /**
     * 给一任意字符串，获取所有中括号内的值
     * @param { string } str
     */
    const 解开中括号 = (str: string): string[] | undefined => {

        // a.b[c.d[0]].e[0].f[g[h[i.j.k]]].l =>
        // ["c.d[0]", "0", "g[h[i.j.k]]"] =>
        // ["c.d", "0", "g", "h[i.j.k]"] =>
        // ["c.d", "0", "g", "h", "i.j.k"]
        const 取括号内值 = (str: string): string[] | undefined | never => {
            const startPosition = str.indexOf("[");
            if (startPosition === -1) {
                return;
            }

            // 有左括号，无右括号
            const substr = str.substring(startPosition + 1);
            if (!substr.includes("]")) {
                throw new Error(`"${str}"格式错误，缺少“]”`);
            }

            let 左括号count = 1;
            for (let p = 0; p < substr.length; p++) {
                const c = substr[p];
                if (c === '[') {
                    左括号count++;
                } else if (c === ']') {
                    左括号count--;
                }
                if (左括号count === 0) {
                    let 第一个中括号内容 = [substr.substring(0, p)];
                    const 第一个括号内容的第一个左括号位置 = 第一个中括号内容[0].indexOf("[");
                    if (第一个括号内容的第一个左括号位置 !== -1) {
                        const 第一个中括号内容中的第一个括号内容 = 取括号内值(第一个中括号内容[0]);
                        第一个中括号内容.push(...第一个中括号内容中的第一个括号内容!);
                        第一个中括号内容[0] = 第一个中括号内容[0].substring(0, 第一个括号内容的第一个左括号位置);
                    }

                    const 剩余字符 = substr.substring(p + 1);
                    const 剩余括号内值 = 取括号内值(剩余字符);
                    if (!剩余括号内值) {
                        return [...new Set(第一个中括号内容)];
                    }
                    return [...new Set(第一个中括号内容.concat(剩余括号内值))];
                }
            }
            if (左括号count !== 0) {
                throw new Error(`"${str}"格式错误，缺少${左括号count}个“]”`);
            }
        }

        return 取括号内值(str);
    }

    /**
     * 给一个字符串（不包含中括号）
     * @param { string } str
     */
    const 获取标识符相关参数 = (str: string): string | undefined => {
        if (str.startsWith(".")) {
            throw new Error(`"${str}"格式错误，不能以“.”开头`);
        }
        const [key] = str.split(".");
        // 数字
        if (!isNaN(parseFloat(key)) || !isNaN(parseInt(key))) {
            return undefined;
        }
        return key;
    }

    if (!str) {
        return null;
    }
    let args: string[] = [];
    let 处理后文本 = str.replace(/\s/g, "");
    const 分割后文本 = 处理后文本.split(/\=|\+|\-|\*|\/|\%|\<|\>|\<|\>|\&|\^|\||\?|\:/g);

    for (let expressionBlock of 分割后文本) {
        // 内容较多的 block
        if (expressionBlock.includes("[")) {
            const 中括号中的值的数组 = 解开中括号(expressionBlock);
            for (const 值 of 中括号中的值的数组!) {
                const argName = 获取标识符相关参数(值);
                if (argName) {
                    args.push(argName);
                }
            }
            // 中括号前面的内容
            const identifier = expressionBlock.substring(0, expressionBlock.indexOf("["));
            const argName = 获取标识符相关参数(identifier);
            if (argName) {
                args.push(argName);
            }
            continue;
        }

        // 普通 block
        const argName = 获取标识符相关参数(expressionBlock);
        if (argName) {
            args.push(argName);
        }
    }

    const f = new Function(args.join(","), `
        return (${处理后文本});
    `);
    return {
        f,
        needArgs: args
    }
}
function runFunction(f: Function, args: any[]) {
    if (!f) {
        throw new Error("f为空");
    }
    const result = f(...args);
    return typeof result === "object" ? JSON.stringify(result) : result;
}
function runCode(code: string, argsValue?: (argNames: string[]) => any[]) {
    if (!code) {
        throw new Error("缺少表达式");
    }
    const { f, needArgs } = 编译表达式(code)!;
    if (!argsValue) {
        if (needArgs) {
            throw new Error("缺少参数");
        }
        return runFunction(f, []);
    }
    return runFunction(f, argsValue(needArgs));
}


function* getCodeFromText(textContent: string) {
    let startIndex, codeEndIndex;
    let text = textContent;
    while((startIndex = text.indexOf("{{")) !== -1) {
        codeEndIndex = text.indexOf("}}", startIndex);
        if (codeEndIndex === -1) break;
        const code = text.substring(startIndex + 2, codeEndIndex).trim();
        const textEndIndex = codeEndIndex + 2;
        yield {
            beforeCodeText: text.substring(0, startIndex),
            code,
        };
        text = text.substring(textEndIndex);
        // console.log(text);
    }
}


const directiveMap = new Map<string, EUVDirective>();
export function registerDirective(
    info: EUVDirectiveInformation,
    directive: EUVDirective
) {
    if (!info) {
        throw new Error("name must be a string or object");
    }
    if (!info.name) {
        throw new Error(`name is ${info.name}`);
    }
    if (!info.name.startsWith("e-")) {
        info.name = "e-" + info.name;
    }
    directive.getInformation = () => info;
    directiveMap.set(info.name, directive);
    if (info.aliasName) {
        directiveMap.set(info.aliasName, directive);
    }
}

export function compileDOMElement(node: HTMLElement): EUVNode | null {
    if (node.nodeType === Node.TEXT_NODE) {
        if (!node.textContent || /^\s+$/g.test(node.textContent)) {
            return null;
        }
        return {
            tag: node.nodeName,
            type: node.nodeType,
            props: {},
            text: node.textContent ? node.textContent : "",
            element: node
        };
    }
    const props: { [key: string]: string } = {};
    for (const a of node.attributes) {
        props[a.name] = a.value;
    }
    const children = new Array<EUVNode>();
    for (let i = 0; i < node.childNodes.length; i++) {
        if (node.childNodes[i].nodeType === Node.COMMENT_NODE) {
            continue;
        }
        const child = compileDOMElement(node.childNodes[i] as HTMLElement);
        if (child) {
            children.push(child);
        }
    }
    return {
        tag: node.nodeName,
        type: node.nodeType,
        props,
        children,
        element: node
    };
}


function compileEUVTextNode(
    node: EUVNode,
    root: HTMLElement,
    argsValue: (argNames: string[]) => any[]
) {
    if (node.type !== Node.TEXT_NODE) {
        throw new Error("wrong node type");
    }

    if (!node.text) {
        node.render = () => {};
        return;
    }

    if (!node.element) {
        node.element = document.createTextNode(node.text) as any;
    }
    root.appendChild(node.element!);

    const gen = getCodeFromText(node.text!);
    const result = gen.next();
    if (!argsValue || result.done) {
        node.render = () => {
            node.element!.textContent = node.text!;
        };
        return;
    }

    node.render = () => {
        let newText = "";
        const gen = getCodeFromText(node.text!);
        let result = gen.next();
        for (;!result.done;result = gen.next()) {
            const code = result.value;
            const codeResult = runCode(code.code, argsValue);
            newText += code.beforeCodeText + codeResult;
        }
        node.element!.textContent = newText;
    };
}

/**
 * 获取 EUVNode 渲染函数（不渲染子节点）
 * @param node
 * @param root
 * @returns
 */
export function compileEUVNode(
    node: EUVNode,
    root: HTMLElement,
    argsValue: (argNames: string[]) => any[]
) {
    // 文本节点
    if (node.type === Node.TEXT_NODE) {
        compileEUVTextNode(node, root, argsValue);
        return;
    }

    // 元素节点
    if (!node.element) {
        node.element = document.createElement(node.tag);
    }
    // compile child node
    if (node.children) {
        for (const child of node.children) {
            compileEUVNode(child, node.element!, argsValue);
        }
    }

    const directiveMountedF: Function[] = [];
    const directiveUpdateF: Function[] = [];

    for (const key in node.props) {
        const value = node.props[key];
        let directiveName = "";
        if (key.startsWith(":")) {
            directiveName = ":";
        } else if (key.startsWith("@")) {
            directiveName = "@";
        } else {
            directiveName = key.split(":")[0];
        }
        const directive = directiveMap.get(directiveName);
        if (directive) {
            const info = directive.getInformation!();
            if (directive.mounted) {
                let mountedFn;
                if (info.calculatedValue) {
                    mountedFn = () => {
                        const calculatedValue = runCode(value, argsValue);
                        directive.mounted!(node.element!, {name: key, value, calculatedValue});
                    };
                } else {
                    mountedFn = () => {
                        directive.mounted!(node.element!, {name: key, value});
                    };
                }
                directiveMountedF.push(mountedFn);
            }
            if (directive.updated) {
                let updateFn;
                if (info.calculatedValue) {
                    updateFn = () => {
                        const calculatedValue = runCode(value, argsValue);
                        directive.updated!(node.element!, {name: key, value, calculatedValue}, node);
                    };
                } else {
                    updateFn = () => {
                        directive.updated!(node.element!, {name: key, value}, node);
                    };
                }
                directiveUpdateF.push(updateFn);
            }
        } else {
            try {
                node.element.setAttribute(key, value);
            } catch (e) {}
        }
    }

    // mounted
    if (node.element === root) {
        return;
    }
    root.appendChild(node.element);
    directiveMountedF.forEach(mounted => mounted());
    node.render = () => {
        directiveUpdateF.forEach(update => update());
    }
}

export default {
    registerDirective,
    compileDOMElement,
    compileEUVNode
}


// compile root 元素
// 遍历 children compile ENode

